#include "iserialisable.h"
#include "metaclass.h"
#include "system/istream.h"

#define GET_MEMBER_ADDRESS(object, offset) \
	(void*)((char*)object + offset)

#define MIN(a, b) \
	((a < b) ? a : b)

namespace gecko
{

	//---------------------------------------------------------------------------------------------

	RTTI_IMPL(ISerialisable, IObject)

	//---------------------------------------------------------------------------------------------

	typedef size_t (*SerialiseFn)(IStream& stream, void* memberAddress, size_t size);
	size_t SerialiseSimpleData(IStream& stream, void* memberAddress, size_t size);
	size_t SerialiseString(IStream& stream, void* memberAddress, size_t size);
	size_t SerialiseClass(IStream& stream, void* memberAddress, size_t size);
	size_t SerialisePointer(IStream& stream, void* memberAddress, size_t  size);

	//---------------------------------------------------------------------------------------------

	static SerialiseFn serialiseStreamFn[MetaClass::NumTypes] =
	{
		SerialiseSimpleData,	// Char
		SerialiseSimpleData,	// Short
		SerialiseSimpleData,	// Bool
		SerialiseSimpleData,	// Int32
		SerialiseSimpleData,	// Int64
		SerialiseSimpleData,	// Float
		SerialiseSimpleData,	// Double
		SerialiseString,		// String
		SerialiseClass,			// Class
		SerialisePointer,		// Pointer
		SerialiseSimpleData,	// Matrix44
	};

	//---------------------------------------------------------------------------------------------

	size_t SerialiseSimpleData( IStream& stream, void* memberAddress, size_t size )
	{
		return stream.Serialise((char*)memberAddress, size);
	}

	//---------------------------------------------------------------------------------------------

	size_t SerialiseString( IStream& stream, void* memberAddress, size_t /*size*/ )
	{
		char* string = (char*)memberAddress;
		stream.SerialiseString(string);
		return strlen(string) + 1;
	}

	//---------------------------------------------------------------------------------------------

	size_t SerialiseClass( IStream& /*stream*/, void* /*memberAddress*/, size_t /*size*/ )
	{
		Error("Not implemented");
		return 0;
	}

	//---------------------------------------------------------------------------------------------

	size_t SerialisePointer( IStream& /*stream*/, void* /*memberAddress*/, size_t /*size*/ )
	{
		// get the pointer
		// check that it is of a type that has a GUID
		// serialise out the GUID
		Error("Not implemented");
		return 0;
	}

	//---------------------------------------------------------------------------------------------

	MetaClass& ISerialisable::GetMetaClass()
	{
		static MetaClass myMetaClass("ISerialisable");
		return myMetaClass;
	}

	//---------------------------------------------------------------------------------------------

	void ISerialisable::Serialise( IStream& stream, MetaClass* fileMetaClass /*= NULL*/ )
	{
		MetaClass & thisMetaClass = GetMetaClass();
		const MetaClass::MemberMap & thisMemberMap = thisMetaClass.GetMemberMap();

		switch (stream.GetMode())
		{
		case IStream::ModeWrite:
			{
				for (MetaClass::MemberMap::const_iterator itr = thisMemberMap.begin(); itr != thisMemberMap.end(); ++itr)
				{
					void * memberAddress = GET_MEMBER_ADDRESS(this, (*itr).dataOffset);
					serialiseStreamFn[(*itr).type](stream, memberAddress, (*itr).size);
				}
			}
			break;

		case IStream::ModeRead:
			{
				if ( fileMetaClass && (thisMetaClass != *fileMetaClass) )
				{
					ClearObjectData(); 

					const MetaClass::MemberMap & fileMemberMap = fileMetaClass->GetMemberMap();

					const int MAX_BUFFER_LEN = 256;
					char readBuffer[MAX_BUFFER_LEN];

					for (MetaClass::MemberMap::const_iterator itr = fileMemberMap.begin(); itr != fileMemberMap.end(); ++itr)
					{
						MetaClass::MemberMap::const_iterator thisMemberItr = thisMemberMap.begin();

						// find the member in the object's meta class (needs to be optimised - hashmap?)
						for (; thisMemberItr != thisMemberMap.end(); ++thisMemberItr)
						{
							if (strcmp((*itr).name, (*thisMemberItr).name) == 0)
							{
								// found it
								break;
							}
						}

						// read data from file into a safe-sized buffer
						size_t bytesRead = serialiseStreamFn[(*itr).type](stream, readBuffer, (*itr).size);

						// if there is no member in the class for this file member, then skip
						if (thisMemberItr == thisMemberMap.end())
						{
							continue;
						}

						// now attempt to put this value into our type of the same name
						// this might be a different type though, say if you changed a short to an int, so we need to cast safely and issue a warning

						if ((*itr).size != (*thisMemberItr).size)
						{
							// issue warning about potential data loss...
						}

						void * memberAddress = GET_MEMBER_ADDRESS(this, (*thisMemberItr).dataOffset);
						size_t smallestMemberSize = MIN(bytesRead, (*thisMemberItr).size);
						memcpy(memberAddress, readBuffer, smallestMemberSize);
					}
				}
			}
			break;
		}
	}

	//---------------------------------------------------------------------------------------------

	void ISerialisable::ClearObjectData()
	{
		// have this set defaults instead of clearing...

		MetaClass & thisMetaClass = GetMetaClass();
		const MetaClass::MemberMap & thisMemberMap = thisMetaClass.GetMemberMap();

		for (MetaClass::MemberMap::const_iterator itr = thisMemberMap.begin(); itr != thisMemberMap.end(); ++itr)
		{
			void * memberAddress = GET_MEMBER_ADDRESS(this, (*itr).dataOffset);
			memset(memberAddress, 0, (*itr).size);
		}
	}

}